import { Vec3, Tween, v3, ITweenOption, tween, Node, Label, Component, SpriteFrame, Sprite, Button, Constructor, instantiate } from "cc";

import { lwarn } from "../logger/Logger";

type ComponentOption<T extends Component> = { [K in keyof T]?: T[K] };
type LabelOption = ComponentOption<Label>;
type SpriteOption = ComponentOption<Sprite>;

declare module "cc" {
    interface Node {
        /** 节点坐标 */
        x: number;
        y: number;
        z: number;
        worldX: number;
        worldY: number;
        worldZ: number;

        /** 本地坐标系移动 */
        DOMove(duration: number, position: Vec3, opts?: ITweenOption): Tween<Node>;
        DOMoveTo(duration: number, position: Vec3, opts?: ITweenOption): Tween<Node>;
        DOMoveX(duration: number, x: number, opts?: ITweenOption): Tween<Node>;
        DOMoveToX(duration: number, x: number, opts?: ITweenOption): Tween<Node>;
        DOMoveY(duration: number, y: number, opts?: ITweenOption): Tween<Node>;
        DOMoveToY(duration: number, y: number, opts?: ITweenOption): Tween<Node>;
        DOMoveZ(duration: number, z: number, opts?: ITweenOption): Tween<Node>;
        DOMoveToZ(duration: number, z: number, opts?: ITweenOption): Tween<Node>;

        /** 世界坐标系移动 */
        DOMoveWorld(duration: number, position: Vec3, opts?: ITweenOption): Tween<Node>;
        DOMoveWorldTo(duration: number, position: Vec3, opts?: ITweenOption): Tween<Node>;
        DOMoveWorldX(duration: number, x: number, opts?: ITweenOption): Tween<Node>;
        DOMoveWorldToX(duration: number, x: number, opts?: ITweenOption);
        DOMoveWorldY(duration: number, y: number, opts?: ITweenOption): Tween<Node>;
        DOMoveWorldToY(duration: number, y: number, opts?: ITweenOption): Tween<Node>;
        DOMoveWorldZ(duration: number, z: number, opts?: ITweenOption): Tween<Node>;
        DOMoveWorldToZ(duration: number, z: number, opts?: ITweenOption): Tween<Node>;

        /** 旋转 */
        eulerX: number;
        eulerY: number;
        eulerZ: number;
        // worldEulerAngles: Vec3;
        // worldEulerX: number;
        // worldEulerY: number;
        // worldEulerZ: number;

        /** 本地坐标系旋转 */
        DORotate(duration: number, eulerAngles: Vec3, opts?: ITweenOption): Tween<Node>;
        DORotateTo(duration: number, eulerAngles: Vec3, opts?: ITweenOption): Tween<Node>;
        DORotateX(duration: number, eulerX: number, opts?: ITweenOption): Tween<Node>;
        DORotateToX(duration: number, eulerX: number, opts?: ITweenOption): Tween<Node>;
        DORotateY(duration: number, eulerY: number, opts?: ITweenOption): Tween<Node>;
        DORotateToY(duration: number, eulerY: number, opts?: ITweenOption): Tween<Node>;
        DORotateZ(duration: number, eulerZ: number, opts?: ITweenOption): Tween<Node>;
        DORotateToZ(duration: number, eulerZ: number, opts?: ITweenOption): Tween<Node>;

        /** 世界坐标系旋转 */
        // DORotateWorld(duration: number, worldEulerAngles: Vec3, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldTo(duration: number, worldEulerAngles: Vec3, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldX(duration: number, worldEulerX: number, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldToX(duration: number, worldEulerX: number, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldY(duration: number, worldEulerY: number, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldToY(duration: number, worldEulerY: number, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldZ(duration: number, worldEulerZ: number, opts?: ITweenOption): Tween<Node>;
        // DORotateWorldToZ(duration: number, worldEulerZ: number, opts?: ITweenOption): Tween<Node>;

        /** 缩放 */
        scaleX: number;
        scaleY: number;
        scaleZ: number;
        worldScaleX: number;
        worldScaleY: number;
        worldScaleZ: number;

        /** 本地坐标系缩放 */
        DOScale(duration: number, scale: Vec3, opts?: ITweenOption): Tween<Node>;
        DOScaleTo(duration: number, scale: Vec3, opts?: ITweenOption): Tween<Node>;
        DOScaleX(duration: number, scaleX: number, opts?: ITweenOption): Tween<Node>;
        DOScaleToX(duration: number, scaleX: number, opts?: ITweenOption): Tween<Node>;
        DOScaleY(duration: number, scaleY: number, opts?: ITweenOption): Tween<Node>;
        DOScaleToY(duration: number, scaleY: number, opts?: ITweenOption): Tween<Node>;
        DOScaleZ(duration: number, scaleZ: number, opts?: ITweenOption): Tween<Node>;
        DOScaleToZ(duration: number, scaleZ: number, opts?: ITweenOption): Tween<Node>;

        /** 世界坐标系缩放 */
        DOScaleWorld(duration: number, worldScale: Vec3, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldTo(duration: number, worldScale: Vec3, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldX(duration: number, worldScaleX: number, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldToX(duration: number, worldScaleX: number, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldY(duration: number, worldScaleY: number, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldToY(duration: number, worldScaleY: number, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldZ(duration: number, worldScaleZ: number, opts?: ITweenOption): Tween<Node>;
        DOScaleWorldToZ(duration: number, worldScaleZ: number, opts?: ITweenOption): Tween<Node>;

        /** 组件快捷函数 */
        /** 根据路径获取子节点 */
        Child(path?: string): Node | null;
        Component<T extends Component>(path?: string | Constructor<T>, component?: Constructor<T>): T;
        /** 设置文本和其他属性 */
        Label(text: string | LabelOption, opts?: LabelOption): void;
        /** 设置子节点文本和其他属性 */
        LabelChild(path: string, text: string | LabelOption, opts?: LabelOption): void;
        /** 设置Sprite图片和其他属性 */
        Sprite(spriteFrame: SpriteFrame | SpriteOption, opts?: SpriteOption): void;
        /** 设置子节点Sprite图片和其他属性 */
        SpriteChild(path: string, spriteFrame?: SpriteFrame | SpriteOption, opts?: SpriteOption): void;

        /** 事件监听 */
        OnChild(path: string, eventName: string, callback: Function, target?: any, useCapture?: any): void;
        EmitChild(path: string, eventName: string, ...args: any): void;
        /** 组件事件监听 */
        Click(...args: any): void;
        ClickChild(path: string, ...args: any): void;
        OnClick(callback: Function, target?: any, useCapture?: any): void;
        OnceClick(callback: Function, target?: any, useCapture?: any): void;
        OnClickChild(path: string, callback: Function, target?: any, useCapture?: any): void;
        OnceClickChild(path: string, callback: Function, target?: any, useCapture?: any): void;

        Copy(parent: Node): Node;
    }
}

export default class NodeExt {
    public static Init() {
        //@ts-ignore
        if (Node.prototype["$__sgameExtension__"]) {
            return;
        }

        //@ts-ignore
        Node.prototype["$__sgameExtension__"] = true;
        this._InitPositionExt();
        this._InitRotationExt();
        this._InitScaleExt();
        this._InitComponentExt();
        this._InitEventExt();
        this._InitUtilExt();
    }

    private static _InitPositionExt() {
        /** start: 扩展属性 */
        // position x/y/z
        Object.defineProperty(Node.prototype, "x", {
            get(): number {
                return this.position.x;
            },
            set(x: number) {
                this.position = v3(x, this.position.y, this.position.z);
            }
        });

        Object.defineProperty(Node.prototype, "y", {
            get(): number {
                return this.position.y;
            },
            set(y: number) {
                this.position = v3(this.position.x, y, this.position.z);
            }
        });

        Object.defineProperty(Node.prototype, "z", {
            get(): number {
                return this.position.z;
            },
            set(z: number) {
                this.position = v3(this.position.x, this.position.y, z);
            }
        });

        // worldPosition x/y/z
        Object.defineProperty(Node.prototype, "worldX", {
            get(): number {
                return this.worldPosition.x;
            },
            set(x: number) {
                this.worldPosition = v3(x, this.worldPosition.y, this.worldPosition.z);
            }
        });

        Object.defineProperty(Node.prototype, "worldY", {
            get(): number {
                return this.worldPosition.y;
            },
            set(y: number) {
                this.worldPosition = v3(this.worldPosition.x, y, this.worldPosition.z);
            }
        });

        Object.defineProperty(Node.prototype, "worldZ", {
            get(): number {
                return this.worldPosition.z;
            },
            set(z: number) {
                this.worldPosition = v3(this.worldPosition.x, this.worldPosition.y, z);
            }
        });
        /** end: 扩展属性 */

        // doMove[To][x/y/z]
        Node.prototype.DOMove = function (duration: number, position: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { position }, opts);
        }

        Node.prototype.DOMoveTo = function (duration: number, position: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { position }, opts);
        }

        Node.prototype.DOMoveX = function (duration: number, x: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { x }, opts);
        }

        Node.prototype.DOMoveToX = function (duration: number, x: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { x }, opts);
        }

        Node.prototype.DOMoveY = function (duration: number, y: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { y }, opts);
        }

        Node.prototype.DOMoveToY = function (duration: number, y: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { y }, opts);
        }

        Node.prototype.DOMoveZ = function (duration: number, z: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { z }, opts);
        }

        Node.prototype.DOMoveToZ = function (duration: number, z: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { z }, opts);
        }

        // doMoveWorld[To][x/y/z]
        Node.prototype.DOMoveWorld = function (duration: number, worldPosition: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldPosition }, opts);
        }

        Node.prototype.DOMoveWorldTo = function (duration: number, worldPosition: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldPosition }, opts);
        }

        Node.prototype.DOMoveWorldX = function (duration: number, worldX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldX }, opts);
        }

        Node.prototype.DOMoveWorldToX = function (duration: number, worldX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldX }, opts);
        }

        Node.prototype.DOMoveWorldY = function (duration: number, worldY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldY }, opts);
        }

        Node.prototype.DOMoveWorldToY = function (duration: number, worldY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldY }, opts);
        }

        Node.prototype.DOMoveWorldZ = function (duration: number, worldZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldZ }, opts);
        }

        Node.prototype.DOMoveWorldToZ = function (duration: number, worldZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldZ }, opts);
        }
    }

    private static _InitRotationExt() {
        /** start: 扩展属性 */
        // rotation x/y/z
        Object.defineProperty(Node.prototype, "eulerX", {
            get(): number {
                return this.eulerAngles.x;
            },
            set(eulerX: number) {
                this.eulerAngles = v3(eulerX, this.eulerAngles.y, this.eulerAngles.z);
            }
        });

        Object.defineProperty(Node.prototype, "eulerY", {
            get(): number {
                return this.eulerAngles.y;
            },
            set(eulerY: number) {
                this.eulerAngles = v3(this.eulerAngles.x, eulerY, this.eulerAngles.z);
            }
        });

        Object.defineProperty(Node.prototype, "eulerZ", {
            get(): number {
                return this.eulerAngles.z;
            },
            set(eulerZ: number) {
                this.eulerAngles = v3(this.eulerAngles.x, this.eulerAngles.y, eulerZ);
            }
        });

        // worldRotation x/y/z
        Object.defineProperty(Node.prototype, "worldEulerAngles", {
            get(): Vec3 {
                let vec3: Vec3 = v3(0, 0, 0);
                this.worldRotation.getEulerAngles(vec3);
                return vec3;
            },
            set(worldEulerAngles: Vec3) {
                this.setWorldRotationFromEuler(worldEulerAngles.x, worldEulerAngles.y, worldEulerAngles.z);
            }
        })

        // worldRotation x/y/z
        Object.defineProperty(Node.prototype, "worldEulerX", {
            get(): number {
                return this.worldEulerAngles.x;
            },
            set(worldeulerX: number) {
                this.worldEulerAngles = v3(worldeulerX, this.worldEulerAngles.y, this.worldEulerAngles.z);
            }
        });

        Object.defineProperty(Node.prototype, "worldEulerY", {
            get(): number {
                return this.worldEulerAngles.y;
            },
            set(worldeulerY: number) {
                this.worldEulerAngles = v3(this.worldEulerAngles.x, worldeulerY, this.worldEulerAngles.z);
            }
        });

        Object.defineProperty(Node.prototype, "worldEulerZ", {
            get(): number {
                return this.worldEulerAngles.z;
            },
            set(worldeulerZ: number) {
                this.worldEulerAngles = v3(this.worldEulerAngles.x, this.worldEulerAngles.y, worldeulerZ);
            }
        });
        /** end: 扩展属性 */

        Node.prototype.DORotate = function (duration: number, eulerAngles: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { eulerAngles }, opts);
        }

        Node.prototype.DORotateTo = function (duration: number, eulerAngles: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { eulerAngles }, opts);
        }

        Node.prototype.DORotateX = function (duration: number, eulerX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { eulerX }, opts);
        }

        Node.prototype.DORotateToX = function (duration: number, eulerX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { eulerX }, opts);
        }

        Node.prototype.DORotateY = function (duration: number, eulerY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { eulerY }, opts);
        }

        Node.prototype.DORotateToY = function (duration: number, eulerY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { eulerY }, opts);
        }

        Node.prototype.DORotateZ = function (duration: number, eulerZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { eulerZ }, opts);
        }

        Node.prototype.DORotateToZ = function (duration: number, eulerZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { eulerZ }, opts);
        }

        // Node.prototype.DORotateWorld = function(duration: number, worldEulerAngles: Vec3, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).by(duration, { worldEulerAngles }, opts);
        // }

        // Node.prototype.DORotateWorldTo = function(duration: number, worldEulerAngles: Vec3, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).to(duration, { worldEulerAngles }, opts);
        // }

        // Node.prototype.DORotateWorldX = function(duration: number, worldEulerX: number, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).by(duration, { worldEulerX }, opts);
        // }

        // Node.prototype.DORotateWorldToX = function(duration: number, worldEulerX: number, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).to(duration, { worldEulerX }, opts);
        // }

        // Node.prototype.DORotateWorldY = function(duration: number, worldEulerY: number, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).by(duration, { worldEulerY }, opts);
        // }

        // Node.prototype.DORotateWorldToY = function(duration: number, worldEulerY: number, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).to(duration, { worldEulerY }, opts);
        // }

        // Node.prototype.DORotateWorldZ = function(duration: number, worldEulerZ: number, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).by(duration, { worldEulerZ }, opts);
        // }

        // Node.prototype.DORotateWorldToZ = function(duration: number, worldEulerZ: number, opts?: ITweenOption): Tween<Node> {
        //     return tween(this).to(duration, { worldEulerZ }, opts);
        // }
    }

    private static _InitScaleExt() {
        /** start: 扩展属性 */
        // scale x/y/z
        Object.defineProperty(Node.prototype, "scaleX", {
            get(): number {
                return this.scale.x;
            },
            set(scaleX: number) {
                this.scale = v3(scaleX, this.scale.y, this.scale.z);
            }
        });

        Object.defineProperty(Node.prototype, "scaleY", {
            get(): number {
                return this.scale.y;
            },
            set(scaleY: number) {
                this.scale = v3(this.scale.x, scaleY, this.scale.z);
            }
        });

        Object.defineProperty(Node.prototype, "scaleZ", {
            get(): number {
                return this.scale.z;
            },
            set(scaleZ: number) {
                this.scale = v3(this.scale.x, this.scale.y, scaleZ);
            }
        });

        Object.defineProperty(Node.prototype, "worldScaleX", {
            get(): number {
                return this.worldScale.x;
            },
            set(worldScaleX: number) {
                this.worldScale = v3(worldScaleX, this.worldScale.y, this.worldScale.z);
            }
        });

        Object.defineProperty(Node.prototype, "worldScaleY", {
            get(): number {
                return this.worldScale.y;
            },
            set(worldScaleY: number) {
                this.worldScale = v3(this.worldScale.x, worldScaleY, this.worldScale.z);
            }
        });

        Object.defineProperty(Node.prototype, "worldScaleZ", {
            get(): number {
                return this.worldScale.z;
            },
            set(worldScaleZ: number) {
                this.worldScale = v3(this.worldScale.x, this.worldScale.y, worldScaleZ);
            }
        });
        /** end: 扩展属性 */

        Node.prototype.DOScale = function (duration: number, scale: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { scale }, opts);
        }

        Node.prototype.DOScaleTo = function (duration: number, scale: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { scale }, opts);
        }

        Node.prototype.DOScaleX = function (duration: number, scaleX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { scaleX }, opts);
        }

        Node.prototype.DOScaleToX = function (duration: number, scaleX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { scaleX }, opts);
        }

        Node.prototype.DOScaleY = function (duration: number, scaleY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { scaleY }, opts);
        }

        Node.prototype.DOScaleToY = function (duration: number, scaleY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { scaleY }, opts);
        }

        Node.prototype.DOScaleZ = function (duration: number, scaleZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { scaleZ }, opts);
        }

        Node.prototype.DOScaleToZ = function (duration: number, scaleZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { scaleZ }, opts);
        }

        Node.prototype.DOScaleWorld = function (duration: number, worldScale: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldScale }, opts);
        }

        Node.prototype.DOScaleWorldTo = function (duration: number, worldScale: Vec3, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldScale }, opts);
        }

        Node.prototype.DOScaleWorldX = function (duration: number, worldScaleX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldScaleX }, opts);
        }

        Node.prototype.DOScaleWorldToX = function (duration: number, worldScaleX: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldScaleX }, opts);
        }

        Node.prototype.DOScaleWorldY = function (duration: number, worldScaleY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldScaleY }, opts);
        }

        Node.prototype.DOScaleWorldToY = function (duration: number, worldScaleY: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldScaleY }, opts);
        }

        Node.prototype.DOScaleWorldZ = function (duration: number, worldScaleZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).by(duration, { worldScaleZ }, opts);
        }

        Node.prototype.DOScaleWorldToZ = function (duration: number, worldScaleZ: number, opts?: ITweenOption): Tween<Node> {
            return tween(this).to(duration, { worldScaleZ }, opts);
        }
    }

    private static _InitComponentExt() {
        Node.prototype.Child = function (path?: string): Node | null {
            if (path == undefined) {
                return this;
            }

            return this.getChildByPath(path);
        }

        Node.prototype.Component = function <T extends Component>(path?: string | Constructor<T>, component?: Constructor<T>): T {
            if (typeof path != 'string') {
                component = path;
                path = null;
            }

            return this.Child(path).getComponent(component);
        }

        /** 设置文本 */
        Node.prototype.Label = function (text: string | LabelOption, opts?: LabelOption): void {
            this.LabelChild(null, text, opts);
        }

        /** 设置子节点文本 */
        Node.prototype.LabelChild = function (path: string, text: string | LabelOption, opts?: LabelOption) {
            let node = this.Child(path);
            let labelComponent = node.getComponent(Label);
            if (!labelComponent) {
                lwarn(`node: ${this.name}'s child ${path} has no label component`);
                return;
            }

            if (typeof text == 'string') {
                labelComponent.string = text;
            } else {
                opts = text;
            }

            if (opts != null) {
                for (const key in opts) {
                    labelComponent[key] = opts[key];
                }
            }
        }

        // /** 设置Sprite图片和其他属性 */
        Node.prototype.Sprite = function (spriteFrame: SpriteFrame | SpriteOption, opts?: SpriteOption): void {
            this.SpriteChild(null, spriteFrame, opts);
        }

        /** 设置子节点Sprite图片和其他属性 */
        Node.prototype.SpriteChild = function (path: string, spriteFrame: SpriteFrame | SpriteOption, opts?: SpriteOption): void {
            let node = this.Child(path);
            let spriteComponent: Sprite = node.getComponent(Sprite);
            if (!spriteComponent) {
                lwarn(`node: ${this.name}'s child ${path} has no sprite component`);
                return;
            }

            if (spriteFrame instanceof SpriteFrame) {
                spriteComponent.spriteFrame = spriteFrame as SpriteFrame;
            } else {
                opts = spriteFrame;
            }

            if (opts) {
                for (const key in opts) {
                    spriteComponent[key] = opts[key];
                }
            }
        }
    }

    private static _InitEventExt() {
        Node.prototype.OnClick = function (callback: Function, target?: any, useCapture?: any) {
            this.OnClickChild(null, callback, target, useCapture);
        }

        Node.prototype.OnClickChild = function (path: string, callback: Function, target?: any, useCapture?: any) {
            let node = this.Child(path);
            node.on(Button.EventType.CLICK, callback, target, useCapture);
        }

        Node.prototype.OnceClick = function (callback: Function, target?: any, useCapture?: any) {
            this.OnceClickChild(null, callback, target, useCapture);
        }

        Node.prototype.OnceClickChild = function (path: string, callback: Function, target?: any, useCapture?: any) {
            let node = this.Child(path);
            node.once(Button.EventType.CLICK, callback, target, useCapture);
        }

        Node.prototype.Click = function (...args: any) {
            this.ClickChild(null, ...args);
        }

        Node.prototype.ClickChild = function (path: string, ...args: any) {
            this.Child(path).emit(Button.EventType.CLICK, ...args);
        }

        Node.prototype.OnChild = function (path: string, eventName: string, callback: Function, target?: any, useCapture?: any): void {
            let node = this.Child(path);
            node.on(eventName, callback, target, useCapture);
        }

        Node.prototype.EmitChild = function (path: string, eventName: string, ...args: any): void {
            let node = this.Child(path);
            node.emit(eventName, ...args);
        }
    }

    private static _InitUtilExt() {
        Node.prototype.Copy = function (parent: Node) {
            let node: Node = instantiate(this);
            node.setParent(parent ?? this.parent);
            return node;
        }
    }
}